iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 4
1
Modern Web

從比入門再往前一點開始,一直到深入React.js系列 第 4

【Day.04】為什麼需要框架 - 用觀察者模式,打造更好用的元件

  • 分享至 

  • xImage
  •  

(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問


昨天做的UI怪怪的

當我們得意地看著在前面的程式碼,卻發現一件不符合直覺的事情:

不管目前menu是開是關,外部按鍵永遠顯示「開啟選單」

要怎麼解決這個問題呢?我們現在想做的事情,其實可以用一句白話文來表達:

「我們想要一直看著isOpen這個變數,每當他被改變的那瞬間,就改變button的文字。」

新手在這個時候一般會想到無限迴圈,但這是一個很爛又很浪費效能的方法。

於是,過去的工程師就想出了「觀察者模式」。

觀察者模式 - 另一個Design Pattern

「觀察者模式」的概念有點像是一個偵查兵,假設我們要一直看著的事叫做「開戰」好了,它做的事情就是:

  1. 蒐集所有「當訊號發生(ex:開戰)時,會需要觸發的事情」(ex: 士兵拿裝備、將領回指揮中心)。
  2. 預先設定當訊號發生時,必定會引發一個統整的過程 (ex:偵查兵會觸發警報鈴)
  3. 把所有1蒐集的內容定義在2觸發的過程當中 (ex:警報鈴會導致士兵和將領都會做對應的動作)

對應在我們的程式碼就是

  1. 蒐集所有「當isOpen被改變時,會需要觸發的外部函式」。
  2. 定義一個函式setIsOpen(),並規定使用者只能用它來改變isOpen
  3. setIsOpen()的定義內容中,在改變isOpen後呼叫所有1中蒐集的函式,也就是:
    let isOpenEffectListenerArr = [ funcA, funcB ];
    function setIsOpen(){
        isOpen = !isOpen;
    
        //在這裡呼叫所有isOpenEffectListener
        isOpenEffectListenerArr.forEach((func) => {
            func();
        });
    }
    

這樣當isOpen被改變時,所有需要和isOpen有關的外部函式都能被觸發。

如何用觀察者模式修改我們的程式碼呢?

雖然Javascript與瀏覽器之間有客製Event的API,不過為了方便理解,我們這邊先簡單手刻一個這個過程。

首先,先定義用來蒐集外部Listener的函式

請注意listener可唯一可不唯一,取決於你想要的架構為何。在這裡我為了方便講解,isOpenEffectListener只會同時有一個listener。

    this.setIsOpenEffectListener = function( listener ) {
        this.isOpenEffectListener = listener;
    }

接著在剛剛的this.setIsOpen中,把isOpenEffectListener放到設定完isOpen的地方呼叫

    const self = this;
    
    this.setIsOpenEffectListener = function( listener ) {
        this.isOpenEffectListener = listener;
    }
    
    this.setIsOpen = function() {
        // 「!」會把true變false,false變true
        isOpen = !isOpen;
        
        /*----- 呼叫Listener -----*/
        if(self.isOpenEffectListener) //避免isOpenEffectListener沒有被定義
            self.isOpenEffectListener(isOpen);

        if(isOpen){
            menu.style.display = "block";
            menuBtn.textContent="^";
        }
        else{
            menu.style.display = "none";
            menuBtn.textContent="V";
        }  
    };

最後回到js/index.js,設定一個listener,用來改變button對應isOpen要顯式的文字

const controlBtn = document.createElement('button');
controlBtn.onclick = function(){
    menuInstance.setIsOpen();
};
controlBtn.textContent = "開啟選單";


// 設定listener
menuInstance.setIsOpenEffectListener(function(isOpenNewValue){
    if(isOpenNewValue)
        controlBtn.textContent = "關閉選單";
    else
        controlBtn.textContent = "開啟選單";
})

這樣你滿意了吧?

還沒有耶,目前我們只是讓元件的功能更多樣化,但是使用上還能有更彈性的地方。下一篇我們要來討論切割架構


上一篇
【Day.03】為什麼需要框架 - 打造元件共同功能
下一篇
【Day.05】為什麼需要框架 - 分離架構、自製Menu、MenuItem
系列文
從比入門再往前一點開始,一直到深入React.js30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言